package com.template.service.impl;

import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.template.common.exception.CommonException;
import com.template.common.lang.Const;
import com.template.domain.entity.SysMenu;
import com.template.domain.entity.SysUser;
import com.template.domain.entity.UserRole;
import com.template.domain.param.AuthParams;
import com.template.domain.param.UserParams;
import com.template.domain.vo.Sys;
import com.template.domain.vo.SysUserVo;
import com.template.mapper.SysUserMapper;
import com.template.service.SysMenuService;
import com.template.service.RoleMenuService;
import com.template.service.UserRoleService;
import com.template.service.SysUserService;
import com.template.utils.CaptchaUtils;
import com.template.utils.CollectionUtils;
import com.template.utils.JwtUtils;
import com.template.utils.SecurityUtils;
import com.template.utils.redis.StringOps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.template.domain.entity.table.RoleMenuTableDef.ROLE_MENU;
import static com.template.domain.entity.table.SysMenuTableDef.SYS_MENU;
import static com.template.domain.entity.table.SysRoleTableDef.SYS_ROLE;
import static com.template.domain.entity.table.SysUserTableDef.SYS_USER;
import static com.template.domain.entity.table.UserRoleTableDef.USER_ROLE;

/**
 * 服务层实现。
 *
 * @author dong
 * @since 2023-09-03
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    private final StringOps stringOps;

    private final CaptchaUtils captchaUtils;

    private final JwtUtils jwtUtils;

    private final UserRoleService userRoleService;

    private final RoleMenuService roleMenuService;

    private final SysMenuService sysMenuService;

    @Override
    public String login(AuthParams.Login param) {
        // 校验验证码
        if (!captchaUtils.verifyCaptcha(param.getKey(), param.getCaptchaCode()))
            throw new CommonException("验证码错误");

        String password = SecurityUtils.md5Encryption(param.getPassword());

        // 查询用户信息
        SysUser user = this.queryChain()
                .select(SYS_USER.ID, SYS_USER.STATUS)
                .where(SYS_USER.ACCOUNT.eq(param.getAccount()))
                .and(SYS_USER.PASSWORD.eq(password))
                .one();
        if (user == null) throw new CommonException("账号或密码不正确");

        // 查看账号是否禁用
        if (!user.getStatus())
            throw new CommonException("账号已停用");

        // 创建并缓存token
        return jwtUtils.createAndCacheToken(user.getId());
    }

    @Override
    public String loginByPhone(AuthParams.LoginByPhone param) {
        // 验证手机号
        String key = Const.cachePhoneCode(param.getPhone());
        String cacheCode = (String) stringOps.get(key);
        if (!Objects.equals(cacheCode, param.getCode()))
            throw new CommonException("验证码错误");

        // 查询用户信息
        // 查看账号是否禁用
        // 创建并缓存token
        return null;
    }

    @Override
    public void updatePwd(Long userId, UserParams.UpdatePwd param) {
        // 验证旧密码
        String oldPassword = SecurityUtils.md5Encryption(param.getOldPassword());
        boolean exists = this.queryChain()
                .where(SYS_USER.ID.eq(userId))
                .and(SYS_USER.PASSWORD.eq(oldPassword))
                .exists();
        if (!exists) throw new CommonException("密码错误！！！");

        // 保存新密码
        String newPassword = SecurityUtils.md5Encryption(param.getNewPassword());
        boolean update = this.updateChain()
                .set(SYS_USER.PASSWORD, newPassword)
                .where(SYS_USER.ID.eq(userId))
                .update();
        if (!update) throw new CommonException(Const.Error.USER_NOT_FIND);

        // 删除登录状态
        jwtUtils.removeToken(userId);
    }

    @Override
    public void updatePwdById(UserParams.Repass params) {
        String password = SecurityUtils.md5Encryption(params.getPassword());
        boolean update = this.updateChain()
                .set(SYS_USER.PASSWORD, password)
                .where(SYS_USER.ID.eq(params.getId()))
                .update();
        if (!update) throw new CommonException(Const.Error.USER_NOT_FIND);

        // 删除登录状态
        jwtUtils.removeToken(params.getId());
    }

    @Override
    public SysUserVo.Login getUserInfo(Long userId) {
        // 获取用户信息
        SysUserVo.Login userInfo = this.queryChain()
                .select(SYS_USER.DEFAULT_COLUMNS)
                .where(SYS_USER.ID.eq(userId))
                .oneAs(SysUserVo.Login.class);

        // 更新用户登录时间
        LocalDateTime now = LocalDateTime.now();
        this.updateChain()
                .set(SYS_USER.LAST_LOGIN, now)
                .where(SYS_USER.ID.eq(userId))
                .update();

        userInfo.setLastLogin(now);

        // 转为Vo
        // 获取用户权限
        List<String> perms = this.getUserPerms(userId);
        userInfo.setPerms(perms);
        return userInfo;
    }

    @Override
    public List<SysUserVo.WithCreateUser> listWithCreateUser() {
        // 查询用户基本信息
        List<SysUserVo.WithCreateUser> userList = this.queryChain()
                .select(SYS_USER.DEFAULT_COLUMNS)
                .listAs(SysUserVo.WithCreateUser.class);
        // 收集createUser的id
        Set<Long> createUserIds = userList.stream()
                .map(SysUserVo.WithCreateUser::getCreateUser)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(createUserIds)) return userList;
        // 查询createUser的姓名
        List<SysUser> list = this.queryChain()
                .select(SYS_USER.ID, SYS_USER.NICKNAME)
                .where(SYS_USER.ID.in(createUserIds))
                .list();
        // 设置createUsername
        Map<Long, String> mapping = CollectionUtils.toMap(list, SysUser::getId, SysUser::getNickname);
        userList.forEach(user -> user.setCreateUsername(mapping.get(user.getCreateUser())));
        return userList;
    }

    @Override
    @Cacheable(value = Const.Cache.AUTHORITY, key = "#userId", condition = "#userId != null")
    public List<String> getUserPerms(Long userId) {
        if (userId == null) return new ArrayList<>();

        List<SysMenu> list = sysMenuService.queryChain()
                .select(SYS_MENU.PERMS)
                .from(SYS_ROLE)
                .join(USER_ROLE).on(SYS_ROLE.ID.eq(USER_ROLE.ROLE_ID))
                .join(ROLE_MENU).on(USER_ROLE.ROLE_ID.eq(ROLE_MENU.ROLE_ID))
                .join(SYS_MENU).on(ROLE_MENU.MENU_ID.eq(SYS_MENU.ID))
                .where(USER_ROLE.USER_ID.eq(userId))
                .and(SYS_ROLE.STATUS.eq(true))
                .and(SYS_MENU.STATUS.eq(true))
                .and(SYS_MENU.PERMS.isNotNull())
                .list();
        return list.stream().map(SysMenu::getPerms).collect(Collectors.toCollection(ArrayList::new));
    }

    @Override
    public List<Sys.Route> getRouteByPerms(Long userId) {
        List<String> perms = this.getUserPerms(userId);
        if (perms == null || perms.size() == 0) return new ArrayList<>();

        List<SysMenu> sysMenuList = sysMenuService.queryChain()
                .select()
                // 查询用户权限对应的菜单 或 没有权限的菜单
                .where(SYS_MENU.PERMS.in(perms).or(SYS_MENU.PERMS.isNull()))
                // 不查按钮
                .and(SYS_MENU.TYPE.ne(SysMenu.Type.Button))
                .list();

        return findChildNode(sysMenuList, null);
    }

    private static List<Sys.Route> findChildNode(List<SysMenu> list, SysMenu node) {

        return list.stream()
                .filter(menu -> {
                    // 若node为null，则返回所有根节点
                    if (node == null) return menu.getParentId() == null;
                    // 否则返回node的子节点
                    return Objects.equals(menu.getParentId(), node.getId());
                })
                // 自定义按照order排序
                .sorted(SysMenu::compare)
                .map(menu -> {
                    // 转化为Route
                    Sys.Route route = Sys.Route.menu2Route(menu);
                    route.setChildren(findChildNode(list, menu));
                    return route;
                })
                .toList();
    }


    //*************** 用户管理 *****************

    @Override
    @Caching(evict = {
            @CacheEvict(value = Const.Cache.AUTHORITY, key = "#user.id", condition = "#user.id != null"),
            // 当更改用户状态时才删除用户token缓存，使用户重新登录
            @CacheEvict(value = Const.Cache.TOKEN, key = "#user.id", condition = "#user.id != null and #user.status != null")
    })
    public boolean updateById(SysUser user) {
        Assert.notNull(user.getId(), Const.Error.PARAMS_INVALID);
        return super.updateById(user);
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value = Const.Cache.AUTHORITY, key = "#id", condition = "#id != null"),
            @CacheEvict(value = Const.Cache.TOKEN, key = "#id", condition = "#id != null")
    })
    public boolean removeById(Serializable id) {
        Assert.notNull(id, Const.Error.PARAMS_INVALID);
        return super.removeById(id);
    }

    @Override
    @CacheEvict(value = Const.Cache.AUTHORITY, key = "#userId", condition = "#userId != null")
    public void assignRole(Long userId, List<Long> roleIds) {
        Assert.notNull(userId, Const.Error.PARAMS_INVALID);
        userRoleService.remove(userRoleService.query().where(USER_ROLE.USER_ID.eq(userId)));
        if (roleIds == null || roleIds.size() == 0) return;
        List<UserRole> userRoleList = roleIds.stream().map(roleId -> new UserRole().setUserId(userId).setRoleId(roleId)).toList();
        userRoleService.saveBatch(userRoleList);
    }

}
